package org.orbroker

import java.sql.{ SQLException, ResultSet, Connection, PreparedStatement }
import org.orbroker.exception._

private[orbroker]trait Executable extends Session {

  protected var uncommittedChanges = false
  override protected def hasUncommittedChanges = uncommittedChanges

  /**
   * Execute a modifying SQL statement, such as INSERT, UPDATE, or DELETE.
   * @param modID The statement id
   * @param parms The parameters
   * @return The number of rows affected
   */
  def execute[G](token: Token[G], parms: (String, Any)*): Int = executeForKeys(token, parms: _*)(null)

  def executeForKeys[G](token: Token[G], parms: (String, Any)*)(keyHandler: G => Unit): Int = {
    val startTime = System.nanoTime
    val ms = getModStatement(token)
    val count = try {
      val (rowsUpdated, sql, values) = 
        ms.execute(token, timeout, connection, toMap(parms), Option(keyHandler))
      callback.onSQL(token.id, sql, values, diffTimeInMicros(startTime))
      rowsUpdated
    } catch {
      case e: SQLException ⇒ throw evaluate(e)
    }
    uncommittedChanges |= count > 0
    count

  }
  
  /**
   * Execute a modifying SQL statement, such as INSERT, that results
   * in a generated key row.
   * @param modID The statement id
   * @param parms The parameters
   * @param keyHandler The generated-key callback handler
   * @return The number of rows affected
   */
  def executeForKey[G](token: Token[G], parms: (String, Any)*): Option[G] = {
    var maybe: Option[G] = None
    executeForKeys(token, parms: _*) { key =>
      if (maybe == None) {
        maybe = Some(key)
      } else {
        throw new MoreThanOneException(token.id, "generated")
      }
    }
    maybe
  }

  /**
   * Execute a modifying SQL statement, such as INSERT, UPDATE, DELETE, 
   * with a batch of values.
   * @param modID The statement id
   * @param batchValues The batch values
   * @param parms Other, optional, parameters
   * @param keyHandler The generated key callback handler
   * @return The number of rows affected
   */
  def executeBatch(token: Token[_], batchValues: (String, Traversable[_]), parms: (String, Any)*): Int = {
    executeBatchForKeys(token, batchValues, parms: _*)(null)
  }

  /**
   * Execute a modifying SQL statement, such as INSERT, UPDATE, DELETE, 
   * with a batch of values, that results in generated keys.
   * @param modID The statement id
   * @param batchValues The batch values
   * @param parms Other, optional, parameters
   * @param keyHandler The generated key callback handler
   * @return The number of rows affected
   */
  def executeBatchForKeys[G](token: Token[G], batchValues: (String, Traversable[_]), parms: (String, Any)*)(keyHandler: G ⇒ Unit): Int = {
    val startTime = System.nanoTime
    val ms = getModStatement(token)
    val count = try {
      val (rowsUpdated, sql, values) = 
        ms.executeBatch(token, timeout, connection, batchValues, toMap(parms), Option(keyHandler))
      callback.onSQLBatch(token.id, sql, values, diffTimeInMicros(startTime))
      rowsUpdated
    } catch {
      case e: SQLException ⇒ throw evaluate(e)
    }
    uncommittedChanges |= count > 0
    count
  }
}
